/*
//
//              INTEL CORPORATION PROPRIETARY INFORMATION
//  This software is supplied under the terms of a license  agreement or
//  nondisclosure agreement with Intel Corporation and may not be copied
//  or disclosed except in  accordance  with the terms of that agreement.
//    Copyright (c) 2007-2008 Intel Corporation. All Rights Reserved.
//
//
*/

#include "umc_defs.h"
#if defined(UMC_ENABLE_AVS_VIDEO_DECODER)

#include "umc_avs_dec_fusion_core.h"
#include "umc_avs_pic.h"
#include "umc_avs_dec_byte_stream.h"
#include "umc_avs_dec_bit_stream.h"
#include "umc_avs_dec_slice_init.h"

#include "umc_automatic_mutex.h"

namespace UMC
{

static
bool FindStartCode(Ipp8u *(&pbSrc), size_t &srcSize)
{
    Ipp8u *pbSrcEnd;

    // calculate the end of sample
    pbSrcEnd = pbSrc + srcSize - 4;

    do
    {
        // we can skip 3 bytes when
        // the next but one byte is greater than 1
        if (1 < pbSrc[2])
        {
            pbSrc += 3;
        }
        // we can skip 2 bytes when
        // the next byte byte is not 0
        else if (0 < pbSrc[1])
        {
            pbSrc += 2;
        }
        // in other case we have to analyze stream byte by byte
        else
        {
            // we found a start code
            if (1 == pbSrc[2])
            {
                if (0 == pbSrc[0])
                {
                    // update the remain bytes
                    srcSize = pbSrcEnd - pbSrc + 4;

                    return true;
                }

                pbSrc += 2;
            }

            pbSrc += 1;
        }

    } while (pbSrc <= pbSrcEnd);

    // skip last bytes
    pbSrc = pbSrcEnd + 4;
    srcSize = 0;

    return false;

} // bool FindStartCode(const Ipp8u *(&pbSrc), size_t &srcSize)

Status AVSDecFusionCore::LoadSource(MediaData *pSrc)
{
    Ipp8u *pbSrc;
    size_t srcSize;
    Status umcRes = UMC_OK;
    Ipp64f dNALTime;
    bool bRes;

    // check end of stream
    if (NULL == pSrc)
    {
        FinalizePicture();
        return UMC_OK;
    }

    pbSrc = (Ipp8u *) pSrc->GetDataPointer();
    srcSize = pSrc->GetDataSize();
    dNALTime = pSrc->GetTime();

    // one by one NAL unit load source into the core
    bRes = FindStartCode(pbSrc, srcSize);
    while (bRes)
    {
        size_t iNALUnitSize, nextSize;
        Ipp8u *pbSrcNext;

        // figure out the unit's size
        iNALUnitSize = srcSize;

        pbSrcNext = pbSrc + 4;
        nextSize = srcSize - 4;
        bRes = FindStartCode(pbSrcNext, nextSize);
        if (bRes)
            iNALUnitSize = pbSrcNext - pbSrc;

        // load NAL unit into the core
        umcRes = LoadNALUnit(pbSrc, iNALUnitSize, dNALTime);
        switch (umcRes)
        {
            // everything is fine, the NAL unit goes well.
            // collected data is not enough for decoding.
        case UMC_ERR_NOT_ENOUGH_DATA:
            // advance to the next start code
            pbSrc = pbSrcNext;
            srcSize = nextSize;
            umcRes = UMC_OK;
            break;

            // the frame has just been completed.
            // repeat the latest NAL unit.
        case UMC_ERR_NOT_ENOUGH_BUFFER:
            umcRes = UMC_OK;
            break;

            // well, we collected whole bunch of frames.
            // let us decode something.
        case UMC_OK:
            bRes = false;
            break;

            // some errors.
            // stop and exit.
        default:
            bRes = false;
            break;
        }
    }

    // update media source
    pSrc->MoveDataPointer((Ipp32s) (pbSrc - (Ipp8u *) pSrc->GetDataPointer()));

    // check error(s)
    if (UMC_OK != umcRes)
        return umcRes;

    if (0 == srcSize)
        return UMC_ERR_NOT_ENOUGH_DATA;

    return UMC_OK;

} // Status AVSDecFusionCore::LoadSource(MediaData *pSrc)

Status AVSDecFusionCore::LoadNALUnit(const Ipp8u *pbSrc, size_t srcSize, Ipp64f dNALTime)
{
    AVSListElement<AVSFrame> *pFrame = GetFreeFrame();

    // check state
    if (NULL == pFrame)
    {
        // everything is fine, we collected enough data for decoding
        if (m_numFrames == m_maxFrames)
            return UMC_OK;
        else
            return UMC_ERR_ALLOC;
    }

    // handle data type
    switch (pbSrc[3])
    {
        // these are valuable information
        // it is possible, that it is from the next frame
    case VIDEO_SEQUENCE_START_CODE:
        if (UMC_OK == FinalizePicture())
            return UMC_ERR_NOT_ENOUGH_BUFFER;
        // decode sequence header
        ParseSequenceHeader(pbSrc, srcSize);
        break;

    case I_PICTURE_START_CODE:
    case BP_PICTURE_START_CODE:
        if (UMC_OK == FinalizePicture())
            return UMC_ERR_NOT_ENOUGH_BUFFER;
        // decode picture header
        ParsePictureHeader(pbSrc, srcSize);
        // set frame time
        pFrame->SetTime(dNALTime);
        break;

    case VIDEO_SEQUENCE_END_CODE:
    case USER_DATA_START_CODE:
    case EXTENSION_START_CODE:
    case VIDEO_EDIT_CODE:
        FinalizePicture();
        break;

        // add video data to the frame
    default:
        if (LAST_SLICE_START_CODE >= pbSrc[3])
        {
            Status umcRes;

            umcRes = AddSliceToPicture(pbSrc, srcSize);
            if (UMC_ERR_NOT_ENOUGH_BUFFER == umcRes)
                FinalizePicture();
            if (UMC_OK != umcRes)
                return umcRes;
        }
        break;
    }

    // we need more source data
    return UMC_ERR_NOT_ENOUGH_DATA;

} // Status AVSDecFusionCore::LoadNALUnit(const Ipp8u *pbSrc, size_t srcSize, Ipp64f dNALTime)

void AVSDecFusionCore::ParseSequenceHeader(const Ipp8u *pbSrc, size_t srcSize)
{
    DecodeAVSSequenceHeader(&m_seqHeader, pbSrc, srcSize);

} // void AVSDecFusionCore::ParseSequenceHeader(const Ipp8u *pbSrc, size_t srcSize)

class AVSDwordWriter
{
public:
    AVSDwordWriter(Ipp32u *pDst)
    {
        m_pBegin = pDst;
        m_pDst = pDst;
        m_nBits = 0;
        m_numFreeBits = 32;
    }

    void PutBits(Ipp32u bits, Ipp32s numBits)
    {
        // the bits register is overflow - need save data to the memory
        if (m_numFreeBits <= numBits)
        {
            // fill empty bit positions
            m_nBits = (m_nBits << m_numFreeBits) |
                      (bits >> (numBits - m_numFreeBits));
            *m_pDst = m_nBits;
            m_pDst += 1;

            // copy remain bits
            m_nBits = bits;
            m_numFreeBits = (32 - (numBits - m_numFreeBits));
        }
        // there is enough room to save bits
        else
        {
            m_nBits = (m_nBits << numBits) | bits;
            m_numFreeBits -= numBits;
        }
    }

    size_t FlushStream(void)
    {
        if (32 != m_numFreeBits)
            PutBits(0, m_numFreeBits);

        return (m_pDst - m_pBegin);
    }

protected:

    Ipp32u *m_pBegin;                                           // (Ipp32u *) pointer to beginning of writing

    Ipp32u *m_pDst;                                             // (Ipp32u *) the current writing position
    Ipp32u m_nBits;                                             // (Ipp32u) cached bits
    Ipp32s m_numFreeBits;                                       // (Ipp32s) number of free bits in the cache
};

static
size_t SwapAndRemovePreventionBits(Ipp32u *pDst, const Ipp8u *pbSrc, size_t srcSize)
{
    AVSDwordWriter dst(pDst);
    const Ipp8u *pbSrcEnd = pbSrc + srcSize;

    // Copy source removing prevention bits
    dst.PutBits(pbSrc[0], 8);
    dst.PutBits(pbSrc[1], 8);
    pbSrc += 2;
    while (pbSrcEnd > pbSrc)
    {
        // we found the first prevention pattern
        if ((2 == pbSrc[0]) &&
            (0 == pbSrc[-1]) &
            (0 == pbSrc[-2]))
        {
            // put zero bits into the destination stream
            dst.PutBits(0, 6);
        }
        else
        {
            // put byte into the destination stream
            dst.PutBits(pbSrc[0], 8);
        }

        pbSrc += 1;
    }

    // finalize the stream
    return dst.FlushStream();

} // size_t SwapAndRemovePreventionBits(Ipp32u *pDst, const Ipp8u *pbSrc, size_t srcSize)

void AVSDecFusionCore::ParsePictureHeader(const Ipp8u *pbSrc, size_t srcSize)
{
    AVS_PICTURE_HEADER picHeader;
    AVS_BIT_STREAM_CONTEXT ctx;
    Ipp32u data[64];
    size_t iDwords;
    bool bRes;

    // check error(s)
    if (VIDEO_SEQUENCE_START_CODE != m_seqHeader.video_sequence_start_code)
        return;

    // prepare source data
    iDwords = SwapAndRemovePreventionBits(data,
                                          pbSrc,
                                          IPP_MIN(srcSize, sizeof(data)));

    // initialize decoding context
    InitializeDecBitStream(&ctx, data, iDwords);

    // decode picture header
    bRes = DecodePicHeader(&picHeader, &ctx, &m_seqHeader);
    if (false == bRes)
        return;

    // check error(s)
    if (0 <= GetBitsLeftToDecode(&ctx))
    {
        m_picHeader = picHeader;
    }

} // void AVSDecFusionCore::ParsePictureHeader(const Ipp8u *pbSrc, size_t srcSize)

Status AVSDecFusionCore::AddSliceToPicture(const Ipp8u *pbSrc, size_t srcSize)
{
    AVSListElement<AVSFrame> *pFrame = m_FreeFrames.GetHead();
    AVSListElement<AVSMemory> *pMemory;
    AVSListElement<AVSSlice> *pSlice;
    size_t iDwords;
    Status umcRes;

    // check error(s)
    // there is no header. Just skip this NAL unit.
    if (VIDEO_SEQUENCE_START_CODE != m_seqHeader.video_sequence_start_code)
        return UMC_OK;
    if ((I_PICTURE_START_CODE != m_picHeader.picture_start_code) &&
        (BP_PICTURE_START_CODE != m_picHeader.picture_start_code))
        return UMC_OK;

    if (m_prevSliceStartCode > pbSrc[3])
        return UMC_ERR_NOT_ENOUGH_BUFFER;

    // allocate a memory piece for the slice
    pMemory = GetFreeMemoryPiece(align_value<size_t> (srcSize, 4));
    if (NULL == pMemory)
    {
        return UMC_ERR_ALLOC;
    }

    // allocate the slice
    pSlice = GetFreeSlice();
    if (NULL == pSlice)
    {
        m_FreeMemory.AddToTail(*pMemory);
        return UMC_ERR_ALLOC;
    }

    // swap source
    iDwords = SwapAndRemovePreventionBits(pMemory->GetBuffer(), pbSrc, srcSize);
    pMemory->SetDataSize(iDwords);

    // decode slice header & initialize slice
    umcRes = InitializeSlice(pSlice,
                             pMemory->GetBuffer(),
                             iDwords,
                             &m_seqHeader,
                             &m_picHeader);
    if (UMC_OK != umcRes)
    {
        m_FreeSlices.AddToTail(*pSlice);
        m_FreeMemory.AddToTail(*pMemory);
        // just skip corrupted data
        return UMC_OK;
    }

    // add these both to the frame
    pFrame->m_Slices.AddToTail(*pSlice);
    pFrame->m_MemoryPieces.AddToTail(*pMemory);
    m_prevSliceStartCode = pbSrc[3];

    return UMC_OK;

} // Status AVSDecFusionCore::AddSliceToPicture(const Ipp8u *pbSrc, size_t srcSize)

Status AVSDecFusionCore::FinalizePicture(void)
{
    AVSListElement<AVSFrame> *pToAdd = m_FreeFrames.GetHead();

    // we met something that make us finish the frame.
    // of course we must have something to decode.
    if ((pToAdd) &&
        (pToAdd->m_Slices.GetHead()))

    {
        Status umcRes;

        // now we need to initialize the collected frame
        umcRes = InitializeFrame(pToAdd);
        if (UMC_OK != umcRes)
        {
            PurgeFrame(pToAdd);
            return UMC_ERR_NOT_ENOUGH_DATA;
        }

        // then move it into the being decoded frame list
        {
            AutomaticMutex guard(m_guard.ExtractHandle());

            // remove the frame from the free frame list
            m_Frames.AddToTail(*m_FreeFrames.ExtractHead());
        }

        // reset the previous slice's number
        m_prevSliceStartCode = 0;

        return UMC_OK;
    }

    // the frame was incomplete
    return UMC_ERR_NOT_ENOUGH_DATA;

} // bool AVSDecFusionCore::FinalizePicture(bool bForce)

} // namespace UMC

#endif // defined(UMC_ENABLE_AVS_VIDEO_DECODER)
